package com.bilibili.juc.cf.one;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.RandomUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*;

/*
 * 演示 CompletableFuture 链式调用
 *
 * */
public class CompleteFutureChainCallFourDemo {
    public static void main(String[] args) throws IOException {
        long startTime = System.currentTimeMillis();
        ExecutorService executor = Executors.newScheduledThreadPool(10, new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread t = new Thread(r);
                t.setName("my-thread-"+t.getId());
                return t;
            }
        });
        ExecutorService executor2 = Executors.newScheduledThreadPool(10, new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread t = new Thread(r);
                t.setName("you-thread-"+t.getId());
                return t;
            }
        });

        CompletableFuture<UserDetail> result = CompletableFuture.supplyAsync(() -> fetchUserId(), executor)
//                .thenCompose(userId -> popUserDetailById(userId)) // 依赖前序结果
                .thenComposeAsync(userId -> fetchUserDetail(userId), executor) // 依赖前序结果
                .thenComposeAsync(userDetail -> popUserDetail(userDetail), executor) // 依赖前序结果
                .handleAsync((userDetail, throwable) -> {
                    if (throwable != null) {
                        System.out.println("****** CompleteFutureChainCallThreeDemo.main throwable=" + throwable + "");
                        return null;
                    }
                    System.out.println(Thread.currentThread().getName());
                    System.out.println(" ****** CompleteFutureChainCallThreeDemo.main handleAsync 1 userDetail=" + userDetail + "");
                    String handleOrder = userDetail.getHandleOrder();
                    if (handleOrder == null) {
                        handleOrder = "handler1";
                    } else {
                        handleOrder = handleOrder + ",handler2";
                    }
                    userDetail.setHandleOrder(handleOrder);
                    return userDetail;
                }, executor)
                .handleAsync((userDetail, throwable) -> {
                    if (throwable != null) {
                        System.out.println("****** CompleteFutureChainCallThreeDemo.main throwable=" + throwable + "");
                        return null;
                    }
                    System.out.println(Thread.currentThread().getName());
                    System.out.println("****** CompleteFutureChainCallThreeDemo.main handleAsync 2 userDetail=" + userDetail + "");
                    String handleOrder = userDetail.getHandleOrder();
                    if (handleOrder == null) {
                        handleOrder = "handler1";
                    } else {
                        handleOrder = handleOrder + ",handler2";
                    }
                    userDetail.setHandleOrder(handleOrder);
                    return userDetail;
                }, executor)
                //.thenAcceptAsync(userDetail -> saveToDB(userDetail), executor)
                ;
        CompletableFuture<UserDetail> result2 = CompletableFuture.supplyAsync(() -> fetchUserId(), executor2)
//                .thenCompose(userId -> popUserDetailById(userId)) // 依赖前序结果
                .thenComposeAsync(userId -> fetchUserDetail(userId), executor2) // 依赖前序结果
                .thenComposeAsync(userDetail -> popUserDetail(userDetail), executor2) // 依赖前序结果
                .handleAsync((userDetail, throwable) -> {
                    if (throwable != null) {
                        System.out.println("****** CompleteFutureChainCallThreeDemo.main throwable=" + throwable + "");
                        return null;
                    }
                    System.out.println(Thread.currentThread().getName());
                    System.out.println(" ****** CompleteFutureChainCallThreeDemo.main handleAsync 1 userDetail=" + userDetail + "");
                    String handleOrder = userDetail.getHandleOrder();
                    if (handleOrder == null) {
                        handleOrder = "handler1";
                    } else {
                        handleOrder = handleOrder + ",handler2";
                    }
                    userDetail.setHandleOrder(handleOrder);
                    return userDetail;
                }, executor2)
                .handleAsync((userDetail, throwable) -> {
                    if (throwable != null) {
                        System.out.println("****** CompleteFutureChainCallThreeDemo.main throwable=" + throwable + "");
                        return null;
                    }
                    System.out.println(Thread.currentThread().getName());
                    System.out.println("****** CompleteFutureChainCallThreeDemo.main handleAsync 2 userDetail=" + userDetail + "");
                    String handleOrder = userDetail.getHandleOrder();
                    if (handleOrder == null) {
                        handleOrder = "handler1";
                    } else {
                        handleOrder = handleOrder + ",handler2";
                    }
                    userDetail.setHandleOrder(handleOrder);
                    return userDetail;
                }, executor2)
                //.thenAcceptAsync(userDetail -> saveToDB(userDetail), executor2)
                ;

        ExecutorService executorAll = Executors.newScheduledThreadPool(10, new ThreadFactory() {
            @Override
            public Thread newThread(Runnable r) {
                Thread t = new Thread(r);
                t.setName("all-thread-"+t.getId());
                return t;
            }
        });
        CompletableFuture<List<UserDetail>> listCompletableFuture = result.thenCombineAsync(result2, (userDetail, userDetail2) -> {
            System.out.println("@@@******@@@ CompleteFutureChainCallThreeDemo.main thenCombineAsync " + Thread.currentThread().getName());
            List<UserDetail> userDetailList = new ArrayList<>();
            userDetailList.add(userDetail);
            userDetailList.add(userDetail2);
            return userDetailList;
        }, executorAll);


        do {
            if (listCompletableFuture.isDone()) {
                System.out.println("@@@@@@ CompleteFutureChainCallThreeDemo.main result.isDone()=" + result.isDone() + "");
                System.out.println("listCompletableFuture==>>  " + listCompletableFuture.join());
                break;
            }
            try {
                TimeUnit.MILLISECONDS.sleep(200);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("****** sleeping ...... time= " + DateUtil.format(new Date(), DatePattern.NORM_DATETIME_MS_PATTERN));
        }while (true);
        System.out.println("****** CompleteFutureChainCallThreeDemo.main cost time=" + (System.currentTimeMillis() - startTime) + "ms");
        System.in.read();
        executor.shutdown(); // 线程池需要关闭
        executor2.shutdown(); // 线程池需要关闭
        executorAll.shutdown(); // 线程池需要关闭
    }

    private static CompletionStage<UserDetail> popUserDetailById(Long userId) {
        System.out.println(Thread.currentThread().getName());
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("****** CompleteFutureChainCallThreeDemo.popUserDetailById userId=" + userId + "");
        return CompletableFuture.supplyAsync(() -> {
            UserDetail userDetail = new UserDetail();
            userDetail.setUserId(userId);
            userDetail.setName(RandomUtil.randomString(10));
            return userDetail;
        });
    }
    private static CompletionStage<UserDetail> popUserDetail(UserDetail userDetail) {
        System.out.println(Thread.currentThread().getName());
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("****** CompleteFutureChainCallThreeDemo.popUserDetail userDetail=" + userDetail + "");
        return CompletableFuture.supplyAsync(() -> {
            userDetail.setName(RandomUtil.randomString(10));
            return userDetail;
        });
    }

    private static CompletionStage<UserDetail> fetchUserDetail(Long userId) {
        System.out.println(Thread.currentThread().getName());
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("****** CompleteFutureChainCallThreeDemo.fetchUserDetail userId=" + userId + "");
        return CompletableFuture.supplyAsync(() -> {
            UserDetail userDetail = new UserDetail();
            userDetail.setUserId(userId);
            return userDetail;

        });
    }

    private static <U> Long fetchUserId() {
        System.out.println(Thread.currentThread().getName());
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("****** CompleteFutureChainCallThreeDemo.fetchUserId");
        return RandomUtil.randomLong(10000L);
    }

    private static void saveToDB(UserDetail userDetail) {
        System.out.println(Thread.currentThread().getName());
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("****** CompleteFutureChainCallThreeDemo.saveToDB userDetail=" + userDetail + "");

    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    static class UserDetail {
        private Long userId;
        private String name;
        private String handleOrder;
    }
}
